﻿//-----------------------------------------------------------------------
// <copyright file="MetadataRegistrationBase.cs" company="Noboru Project">
//      Copyright (c) Noboru Project. All rights reserved.
//
//      This source code is subject to terms and conditions of the New BSD License (BSD).
//      A copy of the license can be found in the License.html file at the root of this 
//      distribution. If you can not locate the New BSD License (BSD), please send an 
//      email to noboru_adm@live.com.
//      By using this source code in any fashion, you are agreeing to be bound by
//      the terms of the New BSD License (BSD) for Noboru.
//
//      You must not remove this notice, or any other, from this software.
// </copyright>
//-----------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Xml.Linq;
using Microsoft.Windows.Design.Metadata;

namespace Noboru.Windows.Controls.Design.Common
{
    /// <include file='Documentation\MetadataRegistrationBaseDocumentation.uex' path='Members/Member[@for="MetadataRegistrationBase.Class"]/*' />
    public class MetadataRegistrationBase
    {
        /// <include file='Documentation\MetadataRegistrationBaseDocumentation.uex' path='Members/Member[@for="MetadataRegistrationBase.XmlResourceName"]/*' />
        protected string XmlResourceName { get; set; }

        /// <include file='Documentation\MetadataRegistrationBaseDocumentation.uex' path='Members/Member[@for="MetadataRegistrationBase.AssemblyFullName"]/*' />
        protected string AssemblyFullName { get; set; }

        /// <include file='Documentation\MetadataRegistrationBaseDocumentation.uex' path='Members/Member[@for="MetadataRegistrationBase.AddAttributes"]/*' />
        protected virtual void AddAttributes(AttributeTableBuilder builder)
        {
        }

        /// <include file='Documentation\MetadataRegistrationBaseDocumentation.uex' path='Members/Member[@for="MetadataRegistrationBase.BuildAttributeTable"]/*' />
        protected virtual AttributeTable BuildAttributeTable()
        {
            AttributeTableBuilder builder = new AttributeTableBuilder();

            this.AddDescriptions(builder);
            this.AddAttributes(builder);
            AddTables(builder);

            return builder.CreateTable();
        }

        [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Design time dll should not fail!")]
        private static void AddTables(AttributeTableBuilder builder)
        {
            Debug.Assert(builder != null, "AddTables is called with null parameter!");

            Assembly asm = Assembly.GetExecutingAssembly();
            foreach (Type t in asm.GetTypes())
            {
                if (t.IsSubclassOf(typeof(AttributeTableBuilder)))
                {
                    try
                    {
                        AttributeTableBuilder atb = (AttributeTableBuilder)Activator.CreateInstance(t);
                        builder.AddTable(atb.CreateTable());
                    }
                    catch (Exception e)
                    {
                        Debug.Assert(false, string.Format(CultureInfo.InvariantCulture, "Exception in AddTables method: {0}", e));
                    }
                }
            }
        }

        private static string ParseDescription(XElement member)
        {
            string desc = null;
            XElement memberDesc = member.Descendants("summary").FirstOrDefault();

            if (memberDesc != null)
            {
                IEnumerable<XNode> nodes = memberDesc.DescendantNodes();

                if (nodes != null)
                {
                    foreach (XNode node in nodes)
                    {
                        if (node.NodeType == System.Xml.XmlNodeType.Text)
                        {
                            desc += node.ToString();
                        }
                        else
                        {
                            string s = node.ToString();
                            int i = s.LastIndexOf('.');
                            int j = s.LastIndexOf('"');

                            if (i != -1 || j != -1)
                            {
                                desc += s.Substring(i + 1, j - i - 1);
                            }
                        }
                    }
                }
            }

            return desc;
        }

        [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Design time dll should not fail.")]
        private void AddDescriptions(AttributeTableBuilder builder)
        {
            Debug.Assert(builder != null, "AddDescriptions is called with null parameter!");

            if (string.IsNullOrEmpty(this.XmlResourceName) || string.IsNullOrEmpty(this.AssemblyFullName))
            {
                return;
            }

            XDocument xdoc = XDocument.Load(new StreamReader(Assembly.GetExecutingAssembly().GetManifestResourceStream(this.XmlResourceName)));
            if (xdoc == null)
            {
                return;
            }

            foreach (XElement member in xdoc.Descendants("member"))
            {
                try
                {
                    string name = (string)member.Attribute("name");
                    bool isType = name.StartsWith("T:", StringComparison.OrdinalIgnoreCase);
                    if (isType ||
                        name.StartsWith("P:", StringComparison.OrdinalIgnoreCase))
                    {
                        int lastDot = name.Length;
                        string typeName;
                        if (isType)
                        {
                            typeName = name.Substring(2); // skip leading "T:"
                        }
                        else
                        {
                            lastDot = name.LastIndexOf('.');
                            typeName = name.Substring(2, lastDot - 2);
                        }

                        typeName += this.AssemblyFullName;

                        Type t = Type.GetType(typeName);
                        if (t != null && t.IsPublic && t.IsClass &&
                            t.IsSubclassOf(typeof(FrameworkElement)))
                        {
                            string desc = ParseDescription(member);
                            desc = desc.Trim();
                            desc = string.Join(" ", desc.Split(new char[] { ' ', '\t', '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries));

                            if (isType)
                            {
                                builder.AddCallback(t, b => b.AddCustomAttributes(new DescriptionAttribute(desc)));
                            }
                            else
                            {
                                string propName = name.Substring(lastDot + 1);
                                PropertyInfo pi = t.GetProperty(propName, BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
                                if (pi != null)
                                {
                                    builder.AddCallback(t, b => b.AddCustomAttributes(propName, new DescriptionAttribute(desc)));
                                }
                            }
                        }
                    }
                }
                catch (Exception e)
                {
                    Debug.Assert(false, string.Format(CultureInfo.InvariantCulture, "Exception: {0}\n For member : {1}", e, member));
                }
            }
        }
    }
}
